#include "matrix.h"
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

static pthread_key_t key;

/**
 * 像线程thread_fun传递的参数
 * @param width 切片宽度
 * @param offset 偏移量，代表子矩阵第一行到原矩阵的距离
 * @param index 切片编号
 * @param m1 矩阵1的指针
 * @param m2 矩阵2的指针
 * @param tar 存储结果的目的矩阵的指针
 */
typedef struct {
  size_t width;
  size_t offset;
  size_t index;
  Matrix *m1;
  Matrix *m2;
  Matrix *tar;
} Arg;

void start() {
  Arg *arg_struct = (Arg *)pthread_getspecific(key);

  size_t offset = (arg_struct->offset);
  size_t width = (arg_struct->width);

  size_t point2[] = {offset, 0, offset + width - 1, arg_struct->m1->col - 1};
  size_t point1[] = {offset, 0, offset + width - 1, arg_struct->tar->col - 1};
  Matrix *sub = get_sub(arg_struct->m1, point2, point2 + 2);
  Matrix *tar_sub = get_sub(arg_struct->tar, point1, point1 + 2);

  dot(sub, arg_struct->m2, tar_sub);

  printf("index = %zu\t ID = %d\n", arg_struct->index, (int)pthread_self());

  destroy_matrix(sub);
  destroy_matrix(tar_sub);
}

/**
 * 设置thread specific data
 * @param arg Arg*
 * @see Arg
 */
void *thread_fun(void *arg) {
  pthread_setspecific(key, arg);
  start();
  return NULL;
}

int main(int argc, char **argv) {
  // 读入矩阵
  FILE *output = fopen("output.txt", "w");

  // 用于thread specific data
  pthread_key_create(&key, NULL);

  // 文件名
  char filename1[100];
  char filename2[100];

  // 线程数
  size_t thread_num;

  // 从命令行读入参数
  int c = 0;
  while ((c = getopt(argc, argv, "a:b:t:")) != -1) {
    switch (c) {
    case 'a':
      strcpy(filename1, optarg);
      break;
    case 'b':
      strcpy(filename2, optarg);
      break;
    case 't':
      thread_num = (size_t)atoi(optarg);
      break;
    case '?':
      fprintf(stderr, "Missing arg: %c", optopt);
      return EXIT_FAILURE;
      break;
    }
  }

  // 读入矩阵
  Matrix *m1 = open_read_matrix(filename1);
  Matrix *m2 = open_read_matrix(filename2);
  Matrix *tar = make_zeros(m1->row, m2->col);

  // 计时函数
  clock_t start, end;
  start = clock();
  size_t width_arr[thread_num];
  size_t offset[thread_num];
  size_t base = m1->row / thread_num;
  for (size_t i = 0; i < thread_num; ++i) {
    offset[i] = 0;
    width_arr[i] = base;
  }
  size_t remain = m1->row % thread_num;
  for (size_t i = 0; i < remain; ++i) {
    ++width_arr[i];
  }
  for (size_t i = 1; i < thread_num; ++i) {
    offset[i] = width_arr[i - 1] + offset[i - 1];
  }

  pthread_t tid[thread_num];
  Arg arg_struct[thread_num];
  for (size_t i = 0; i < thread_num; ++i) {
    arg_struct[i].index = i;
    arg_struct[i].m1 = m1;
    arg_struct[i].m2 = m2;
    arg_struct[i].tar = tar;
    arg_struct[i].width = width_arr[i];
    arg_struct[i].offset = offset[i];
    pthread_create(&tid[i], NULL, thread_fun, (void *)&arg_struct[i]);
  }

  for (size_t i = 0; i < thread_num; ++i) {
    pthread_join(tid[i], NULL);
  }
  // display(output, tar);
  end = clock();

  printf("time=%lf\n", ((double)(end - start)) / CLOCKS_PER_SEC);
  write_matrix(output, tar);
  fclose(output);
  destroy_matrix(m1);
  destroy_matrix(m2);
  destroy_matrix(tar);
}